package cn.yhsoft.fs;

import cn.hutool.core.img.Img;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.io.resource.InputStreamResource;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.yhsoft.fs.config.FsTurboProperties;
import lombok.var;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URLEncoder;
import java.sql.Timestamp;
import java.util.Date;

/**
 * @author LiYong
 */
@FsTurboRestController
@RestController
public class FsTurboController {
    private Logger log = LoggerFactory.getLogger(FsTurboService.class);
    @Autowired
    private FsStorageAdapter storageAdapter;

    @Autowired
    private FsPathResolver pathResolver;

    @Autowired
    private FsTurboService fsTurboService;

    @Autowired
    private FsTurboProperties properties;

    /**
     * 一次性list最大获取的列表数量，暂时固定
     */
    private static int MAX_LIST_SIZE = 100;

    private int getInnerImgScleSize() {
        return properties.getImageThumbSize() == null ? 64 : properties.getImageThumbSize();
    }

    /**
     * 允许的文件类型
     * TODO:这里要更换为application配置文件
     */
    private static String[] allowImageExtentions = new String[]{"jpg", "png", "bmp", "jpeg"};

    private FsResponseBean innerUpload(MultipartFile file, String scenes, String busId, boolean multi, boolean isImage) {
        FsResponseBean responseBean = new FsResponseBean();
        responseBean.setCode(200);
        try {
            //获取文件格式，获取后可以对文件格式进行服务端二次校验操作
            var extName = FileNameUtil.extName(file.getOriginalFilename()).toLowerCase();
            if (properties.getFileSizeMax() != null && properties.getFileSizeMax() < file.getSize()) {
                throw new Exception("上传的文件大小超过了最大的限制大小。限制为:" + properties.getFileSizeMax());
            }
            if (isImage) {
                if (!ArrayUtil.contains(allowImageExtentions, extName)) {
                    throw new Exception("错误的文件格式，当前仅支持图片格式*.png|*.jpg|*.jpeg|*.bmp");
                }
                if (properties.getImageSizeMax() != null && properties.getImageSizeMax() < file.getSize()) {
                    throw new Exception("上传的图片大小超过了最大的限制大小。限制为:" + properties.getImageSizeMax());
                }
            }

            var fileId = IdUtil.objectId();
            var saveFileName = fileId + "." + extName;
            var createTime = new Date();
            //需要计算文件的md5，如果原来文件已经上传过了就不用再保存了
            var md5 = SecureUtil.md5(file.getInputStream());
            var sourceDto = fsTurboService.getSourceFileByMd5(md5, isImage);
            var dtoBuilder = FsTurboDto.builder();
            dtoBuilder.id(fileId).scenes(scenes).busId(busId).source(true).md5(md5).createTime(new Timestamp(createTime.getTime())).saveFileName(saveFileName).fileName(file.getOriginalFilename());
            if (sourceDto == null) {
                var path = storageAdapter.save(pathResolver.getSavePath(scenes, createTime), saveFileName, file.getInputStream());
                dtoBuilder.path(path);
            } else {
                dtoBuilder.path(sourceDto.getPath());
                dtoBuilder.saveFileName(sourceDto.getSaveFileName());
                dtoBuilder.source(false);
            }
            if (isImage && sourceDto == null) {
                //开始进行缩略图生成与存储
                try {
                    InputStream fileStream = file.getInputStream();
                    ByteArrayOutputStream rawOutStream = new ByteArrayOutputStream();
                    byte[] buffer = new byte[4096];
                    int len;
                    while ((len = fileStream.read(buffer)) > -1) {
                        rawOutStream.write(buffer, 0, len);
                    }
                    ByteArrayOutputStream os = new ByteArrayOutputStream();
                    int size = getInnerImgScleSize();
                    Img.from(new ByteArrayInputStream(rawOutStream.toByteArray())).scale(size, size, null).write(os);
                    InputStreamResource inputStreamResource = new InputStreamResource(new ByteArrayInputStream(os.toByteArray()), saveFileName);
                    storageAdapter.save(pathResolver.getThumbSavePath(scenes, createTime), saveFileName, inputStreamResource.getStream());
                } catch (Exception ex) {
                    log.error("从原图生成缩略图失败", ex);
                    throw new Exception("从原图生成缩略图失败");
                }
            }
            if (!multi && StrUtil.isNotEmpty(busId) && StrUtil.isNotEmpty(scenes)) {
                fsTurboService.deleteByScenesAndBus(scenes, busId);
            }
            fsTurboService.insert(dtoBuilder.build(), true);
            responseBean.setId(fileId);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            responseBean.setCode(500);
            responseBean.setMessage(e.getMessage());
        }
        return responseBean;
    }

    @RequestMapping("/upload")
    public FsResponseBean upload(@RequestParam("file") MultipartFile file, @RequestParam("sences") String sences, @RequestParam(value = "busId", required = false) String busId, @RequestParam(value = "multi", required = false) boolean multi) {
        return innerUpload(file, sences, busId, multi, false);
    }

    @RequestMapping("/del")
    public FsResponseBean del(@RequestBody FsTurboDto dto) {
        FsResponseBean responseBean = new FsResponseBean();
        responseBean.setCode(200);
        if (dto == null || StrUtil.isEmpty(dto.getId())) {
            responseBean.setCode(500);
            responseBean.setMessage("请传入id");
        } else {
            try {
                fsTurboService.deleteById(dto.getId());
            } catch (Exception ex) {
                responseBean.setCode(500);
                responseBean.setMessage(ex.getMessage());
            }
        }
        return responseBean;
    }

    @RequestMapping("/image/upload")
    public FsResponseBean uploadImage(@RequestParam("file") MultipartFile file, @RequestParam("sences") String sences, @RequestParam(value = "busId", required = false) String busId, @RequestParam(value = "multi", required = false) boolean multi) {
        return innerUpload(file, sences, busId, multi, true);
    }

    @RequestMapping(value = "/download/{id}")
    public void download(@PathVariable("id") String id, HttpServletResponse response) {
        innerDownload(id, false, response, MediaType.APPLICATION_OCTET_STREAM_VALUE);
    }

    @RequestMapping(value = "/list/{scenes}/{busId}", method = RequestMethod.GET)
    public String[] listIds(@PathVariable("busId") String busId, @PathVariable("scenes") String scenes, HttpServletResponse response) {
        var dtos = fsTurboService.getDtosByBusId(scenes, busId, 0, MAX_LIST_SIZE);
        if (dtos.size() > 0) {
            return dtos.stream().map(FsTurboDto::getId).toArray(String[]::new);
        }
        return new String[0];
    }

    @RequestMapping(value = "/preview/{id}")
    public void preview(@PathVariable("id") String id, HttpServletResponse response) {
        innerDownload(id, false, response, null);
    }

    @RequestMapping(value = "/image/preview/{id}")
    public void previewImage(@PathVariable("id") String id, HttpServletResponse response) {
        innerDownload(id, false, response, null);
    }

    @RequestMapping(value = "/image/preview/bus/{scenes}/{busId}", method = RequestMethod.GET)
    public void previewImage(@PathVariable("busId") String busId, @PathVariable("scenes") String scenes, HttpServletResponse response) {
        //only select top 1
        var dtos = fsTurboService.getDtosByBusId(scenes, busId, 0, 1);
        if (dtos.size() > 0) {
            innerDownload(dtos.get(0).getId(), false, response, null);
        }
    }

    @RequestMapping(value = "/image/thumb/{id}")
    public void thumbImage(@PathVariable("id") String id, HttpServletResponse response) {
        innerDownload(id, true, response, MediaType.IMAGE_PNG_VALUE);
    }

    private String getMediaTypeByPath(String path) {
        var extName = FileNameUtil.extName(path);
        switch (extName) {
            case "jpg":
            case "jpeg":
                return MediaType.IMAGE_JPEG_VALUE;
            case "gif":
                return MediaType.IMAGE_GIF_VALUE;
            case "bmp":
            case "png":
                return MediaType.IMAGE_PNG_VALUE;
            case "pdf":
                return MediaType.APPLICATION_PDF_VALUE;
            default:
                return MediaType.ALL_VALUE;
        }
    }

    /**
     * 下载文件/图片
     *
     * @param id        文件id
     * @param isThumb   是否返回缩略图
     * @param response  返回response
     * @param mediaType 如果传入则使用传入的，如果为null则使用文件名自动进行判断
     */
    private void innerDownload(String id, boolean isThumb, HttpServletResponse response, String mediaType) {
        var dto = fsTurboService.getDtoById(id);
        if (dto == null) {
            return;
        }
        try {
            if (StrUtil.isEmpty(mediaType)) {
                mediaType = getMediaTypeByPath(dto.getFileName());
            }
            //如果是下载附件则需要指定文件名
            if (MediaType.APPLICATION_OCTET_STREAM_VALUE.equals(mediaType)) {
                var encodedFileName = URLEncoder.encode(dto.getFileName(), "utf-8").replaceAll("\\+", "%20");
                response.setHeader("Content-Disposition", "attachment;filename=" + encodedFileName + "");
            }
            response.setContentType(mediaType);
            var path = dto.getPath();
            if (isThumb) {
                path = pathResolver.getThumbSavePath(dto.getPath());
            }
            var inputStream = storageAdapter.getInputStream(path);
            IoUtil.copy(inputStream, response.getOutputStream());
            try {
                inputStream.close();
            } catch (Exception ignored) {

            }

        } catch (Exception ex) {
            log.error("read file:" + dto.getPath() + " fail", ex);
        }
    }
}
